package com.perceptivesoftware.licensing.client.dialogs.useradmin;

import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import com.extjs.gxt.ui.client.data.BaseTreeModel;
import com.extjs.gxt.ui.client.data.ModelData;
import com.extjs.gxt.ui.client.data.ModelStringProvider;
import com.extjs.gxt.ui.client.event.Events;
import com.extjs.gxt.ui.client.event.Listener;
import com.extjs.gxt.ui.client.event.TreePanelEvent;
import com.extjs.gxt.ui.client.store.TreeStore;
import com.extjs.gxt.ui.client.widget.ContentPanel;
import com.extjs.gxt.ui.client.widget.layout.FitLayout;
import com.extjs.gxt.ui.client.widget.treepanel.TreePanel;
import com.extjs.gxt.ui.client.widget.treepanel.TreePanel.CheckCascade;
import com.extjs.gxt.ui.client.widget.treepanel.TreeStyle;
import com.google.gwt.core.client.GWT;
import com.google.gwt.core.client.Scheduler;
import com.google.gwt.core.client.Scheduler.ScheduledCommand;
import com.google.gwt.user.client.Command;
import com.google.gwt.user.client.DeferredCommand;
import com.perceptivesoftware.licensing.client.PerceptiveUISession;
import com.perceptivesoftware.licensing.client.common.DirtyCallbackHandler;
import com.perceptivesoftware.licensing.client.common.util.NPAction;
import com.perceptivesoftware.licensing.client.common.util.NPType;
import com.perceptivesoftware.licensing.client.common.util.PerceptivePermission;
import com.perceptivesoftware.licensing.client.dto.PermissionsDTO;

public class PermissionsTreePanel extends ContentPanel
{

	private final TreeStore<PerceptivePermissionRootModel> treeStore = new TreeStore<PerceptivePermissionRootModel>();
	private final TreePanel<PerceptivePermissionRootModel> treePanel;
	private final Listener<TreePanelEvent<PerceptivePermissionRootModel>> checkedListener;
	private List<PerceptivePermissionRootModel> allModels;
	private PermissionsDTO permissionsDTO;
	private final DirtyCallbackHandler dirtyCallbackHandler;
	private Boolean supressCascade = false;
	private final Boolean allowCascadeToChildren;
	private final Boolean[] permissionsBuffer = new Boolean[25];
	private final PerceptiveUISession nts = PerceptiveUISession.getInstance();

	public PermissionsTreePanel(final DirtyCallbackHandler dirtyCallbackHandler, final Boolean allowCascadeToChildren)
	{
		setHeaderVisible(false);
		this.dirtyCallbackHandler = dirtyCallbackHandler;
		this.allowCascadeToChildren = allowCascadeToChildren;
		for (int i = 0; i < permissionsBuffer.length; i++)
		{
			permissionsBuffer[i] = true;
		}
		treePanel = new TreePanel<PerceptivePermissionRootModel>(treeStore);
		treePanel.setWidth("100%");
		treePanel.setLabelProvider(new ModelStringProvider<PerceptivePermissionRootModel>() {

			@Override
			public String getStringValue(final PerceptivePermissionRootModel model, final String property)
			{
				final StringBuilder sb = new StringBuilder();
				if (model instanceof SimplePerceptivePermissionModel)
				{
					final SimplePerceptivePermissionModel snpm = (SimplePerceptivePermissionModel) model;
					if (allowCascadeToChildren && snpm.getPermission().getHasChangedForCascade())
					{
						sb.append("<span style='color:red;'>");
						sb.append(model.get(property));
						sb.append("</span>");
					}
					else
					{
						sb.append(model.get(property));
					}
				}
				else
				{
					sb.append(model.get(property));
				}
				return sb.toString();
			}
		});
		final TreeStyle treeStyle = new TreeStyle();
		treeStyle.setLeafIcon(null);
		treeStyle.setNodeCloseIcon(null);
		treeStyle.setNodeOpenIcon(null);

		treePanel.setStyle(treeStyle);
		treePanel.setDisplayProperty("name");
		treePanel.setCheckable(true);
		treePanel.setCheckStyle(CheckCascade.NONE);

		checkedListener = new Listener<TreePanelEvent<PerceptivePermissionRootModel>>() {

			public void handleEvent(final TreePanelEvent<PerceptivePermissionRootModel> tpe)
			{
				final PerceptivePermissionRootModel item = tpe.getItem();
				if (item instanceof SimplePerceptivePermissionModel)
				{
					((SimplePerceptivePermissionModel) item).np.setHasPermission(tpe.isChecked());
					((SimplePerceptivePermissionModel) item).np.setHasChangedForCascade(true);
					treeStore.update(item);
					dirtyCallbackHandler.iAmDirty();
				}

				// not else
				if (!supressCascade)
				{
					if (item.getCheckAllChildrenIfChecked())
					{
						for (final PerceptivePermissionRootModel child : treeStore.getChildren(item))
						{
							if (!treePanel.isChecked(child) && treePanel.isChecked(item))
							{
								treePanel.setChecked(child, true);
							}
						}
					}
					if (item.getUnCheckAllChildrenIfUnChecked())
					{
						for (final PerceptivePermissionRootModel child : treeStore.getChildren(item, true))
						{
							if (treePanel.isChecked(child) && !treePanel.isChecked(item))
							{
								treePanel.setChecked(child, false);
							}
						}
					}
				}
				// not else
				if (item.getUncheckAllSiblingsWhenChecked() && treePanel.isChecked(item))
				{
					for (final PerceptivePermissionRootModel nprm : treeStore.getChildren(treeStore.getParent(item)))
					{
						if (nprm != item && treePanel.isChecked(nprm))
						{
							treePanel.setChecked(nprm, false);
						}
					}
				}
				final PerceptivePermissionRootModel parent = treeStore.getParent(item);
				final Boolean tmpSupress = supressCascade;
				supressCascade = true;
				if (treePanel.isChecked(item) && parent != null && parent.getCheckIfAnyChildChecked())
				{
					if (!treePanel.isChecked(parent))
					{
						treePanel.setChecked(parent, true);
					}
				}
				if (!treePanel.isChecked(item) && parent != null && parent.getUnCheckIfNoChildChecked())
				{
					if (treePanel.isChecked(parent))
					{
						boolean check = false;
						for (final PerceptivePermissionRootModel child : treeStore.getChildren(parent))
						{
							if (treePanel.isChecked(child))
							{
								check = true;
							}
						}
						treePanel.setChecked(parent, check);
					}
				}
				supressCascade = tmpSupress;
			}
		};
		treePanel.addListener(Events.Expand, new Listener<TreePanelEvent<PerceptivePermissionRootModel>>() {

			public void handleEvent(final TreePanelEvent<PerceptivePermissionRootModel> tpe)
			{
				final Boolean checkOffItems = false;
				if ((tpe.getItem()).getExpandAllChildrenDeepOnExpand())
				{
					expandFullDepth(tpe.getItem());
				}
				if (checkOffItems)
				{
					checkOffItems();
				}
			}

		});
		setLayout(new FitLayout());
		add(treePanel);
	}

	public void setPermissions(final PermissionsDTO permissionsDTO)
	{
		this.permissionsDTO = permissionsDTO;

		// for debug popup
		final HashMap<String, ArrayList<String>> permHashMap = new HashMap<String, ArrayList<String>>();
		for (final PerceptivePermission np : permissionsDTO.getPermissions())
		{
			final String key = np.getType().toString() + ":" + np.getAction().toString();
			if (permHashMap.get(key) == null)
			{
				permHashMap.put(key, new ArrayList<String>());
			}
			if (np.getHasPermission())
			{
				if (permHashMap.get(key) == null)
				{
					permHashMap.put(key, new ArrayList<String>());
				}
				if (np.getItemCode() != null)
				{
					permHashMap.get(key).add(np.getItemCode());
				}
			}
		}
		final StringBuffer sb = new StringBuffer();
		final ArrayList<String> keys = new ArrayList<String>();
		keys.addAll(permHashMap.keySet());
		Collections.sort(keys);
		for (final String s : keys)
		{
			sb.append(s);
			for (final Object o : permHashMap.get(s))
			{
				sb.append(":");
				sb.append(o);
			}
			sb.append("\n");
		}
		// Window.alert(sb.toString());
		GWT.log(sb.toString(), null);
		// end debug popup code

		treeStore.removeAll();
		SimplePerceptivePermissionModel snpm;
		allModels = new ArrayList<PerceptivePermissionRootModel>();

		/*
		 * Permission Node Tree:
		 */
		final PerceptivePermissionRootModel adminOptions = createTreeNode("Administration", null, true, true, true);
		allModels.add(adminOptions.addAndReturnNode(new SimplePerceptivePermissionModel(NPAction.MANAGE_SECURITY.getDesc(), permissionsDTO.getPermission(
				NPType.ADMIN, NPAction.MANAGE_SECURITY, null))));

		treeStore.add(adminOptions, true); // should recursively add all child
											// nodes

		Scheduler.get().scheduleDeferred(new ScheduledCommand() {

			@Override
			public void execute()
			{
				treePanel.expandAll();
			}
		});
		checkOffItems();
	}

	private PerceptivePermissionRootModel createTreeNode(final String title, final PerceptivePermissionRootModel parentNode, final boolean checkOrUncheckChildrenToMatch, final boolean checkIfAnyChildChecked, final boolean unCheckIfNoChildChecked)
	{
		final PerceptivePermissionRootModel newNode = new PerceptivePermissionRootModel(title);
		newNode.setCheckOrUncheckChildrenToMatch(checkOrUncheckChildrenToMatch);
		newNode.setCheckIfAnyChildChecked(checkIfAnyChildChecked);
		newNode.setUnCheckIfNoChildChecked(unCheckIfNoChildChecked);
		/* Parent node is null if you're creating a top level node. */
		if (parentNode != null)
		{
			parentNode.add(newNode);
		}
		allModels.add(newNode);
		return newNode;
	}

	private void uncheckRecursively(final PerceptivePermissionRootModel parentnpm)
	{
		if (treePanel.isChecked(parentnpm))
		{
			treePanel.setChecked(parentnpm, false);
		}
		for (final PerceptivePermissionRootModel childwipm : treeStore.getChildren(parentnpm))
		{
			uncheckRecursively(childwipm);
		}
	}

	// Contains no actual permission (e.g. general)
	private class PerceptivePermissionRootModel extends BaseTreeModel
	{

		private Boolean checkAllChildrenIfChecked = false;
		private Boolean unCheckAllChildrenIfUnChecked = false;
		private Boolean expandAllChildrenDeepOnExpand = false;
		private Boolean checkIfAnyChildChecked = false;
		private Boolean unCheckIfNoChildChecked = false;
		private Boolean uncheckAllSiblingsWhenChecked = false;
		private Boolean disableIfParentUnchecked = false;

		public PerceptivePermissionRootModel(final String name)
		{
			super();
			this.set("name", name);
		}

		public Boolean getCheckAllChildrenIfChecked()
		{
			return checkAllChildrenIfChecked;
		}

		public void setCheckAllChildrenIfChecked(final Boolean checkAllChildrenIfChecked)
		{
			this.checkAllChildrenIfChecked = checkAllChildrenIfChecked;
		}

		public Boolean getUnCheckAllChildrenIfUnChecked()
		{
			return unCheckAllChildrenIfUnChecked;
		}

		public void setUnCheckAllChildrenIfUnChecked(final Boolean unCheckAllChildrenIfUnChecked)
		{
			this.unCheckAllChildrenIfUnChecked = unCheckAllChildrenIfUnChecked;
		}

		public void setCheckOrUncheckChildrenToMatch(final Boolean checkOrUncheckChildrenToMatch)
		{
			checkAllChildrenIfChecked = checkOrUncheckChildrenToMatch;
			unCheckAllChildrenIfUnChecked = checkOrUncheckChildrenToMatch;
		}

		public Boolean getExpandAllChildrenDeepOnExpand()
		{
			return expandAllChildrenDeepOnExpand;
		}

		public void setExpandAllChildrenDeepOnExpand(final Boolean expandAllChildrenDeepOnExpand)
		{
			this.expandAllChildrenDeepOnExpand = expandAllChildrenDeepOnExpand;
		}

		public Boolean getCheckIfAnyChildChecked()
		{
			return checkIfAnyChildChecked;
		}

		public void setCheckIfAnyChildChecked(final Boolean checkIfAnyChildChecked)
		{
			this.checkIfAnyChildChecked = checkIfAnyChildChecked;
		}

		public Boolean getUnCheckIfNoChildChecked()
		{
			return unCheckIfNoChildChecked;
		}

		public void setUnCheckIfNoChildChecked(final Boolean unCheckIfNoChildChecked)
		{
			this.unCheckIfNoChildChecked = unCheckIfNoChildChecked;
		}

		public Boolean getUncheckAllSiblingsWhenChecked()
		{
			return uncheckAllSiblingsWhenChecked;
		}

		public void setUncheckAllSiblingsWhenChecked(final Boolean uncheckAllSiblingsWhenChecked)
		{
			this.uncheckAllSiblingsWhenChecked = uncheckAllSiblingsWhenChecked;
		}

		public SimplePerceptivePermissionModel addAndReturnNode(final SimplePerceptivePermissionModel snpm)
		{
			add(snpm);
			return snpm;
		}

		public Boolean getDisableIfParentUnchecked()
		{
			return disableIfParentUnchecked;
		}

		public void setDisableIfParentUnchecked(final Boolean disableIfParentUnchecked)
		{
			this.disableIfParentUnchecked = disableIfParentUnchecked;
		}
	}

	// contains one perceptive permission (e.g. System Browser)
	private class SimplePerceptivePermissionModel extends PerceptivePermissionRootModel
	{

		private final PerceptivePermission np;

		SimplePerceptivePermissionModel(final String name, final PerceptivePermission np)
		{
			super(name);
			this.np = np;
		}

		public PerceptivePermission getPermission()
		{
			return np;
		}
	}

	private void checkOffItems()
	{
		// check off items
		final ArrayList<PerceptivePermissionRootModel> shouldBeCheckedItems = new ArrayList<PerceptivePermissionRootModel>();
		for (final PerceptivePermissionRootModel nprm : allModels)
		{
			if (nprm instanceof SimplePerceptivePermissionModel)
			{
				final PerceptivePermission permission = ((SimplePerceptivePermissionModel) nprm).np;
				if (permission.getHasPermission())
				{
					// if (permission.getType().equals(NPType.WORKSPACE) &&
					// (permission.getAction().equals(NPAction.HEADER) ||
					// permission.getAction().equals(NPAction.CONNECTION_GROUP)
					// || permission.getAction().equals(NPAction.FILE_SOURCE)))
					// {
					// final PerceptivePermissionRootModel workspace =
					// (PerceptivePermissionRootModel) nprm.getParent().getParent();
					// final PerceptivePermission workspacePermission =
					// ((SimplePerceptivePermissionModel) workspace).np;
					// if (workspacePermission.getHasPermission()){
					// shouldBeCheckedItems.add(nprm);
					// }
					// }
					// else
					// {TODO uncomment if adding specific items
					shouldBeCheckedItems.add(nprm);
					// }
				}
			}
		}
		treePanel.removeListener(Events.CheckChange, checkedListener);
		final List<PerceptivePermissionRootModel> currentlyCheckedItems = treePanel.getCheckedSelection();
		final Boolean tmpSupress = supressCascade;
		supressCascade = true;
		for (final PerceptivePermissionRootModel nprm : allModels)
		{
			if (shouldBeCheckedItems.contains(nprm) && !currentlyCheckedItems.contains(nprm))
			{
				treePanel.setChecked(nprm, true);
			}
			else if (!shouldBeCheckedItems.contains(nprm) && currentlyCheckedItems.contains(nprm))
			{
				treePanel.setChecked(nprm, false);
			}
		}
		supressCascade = tmpSupress;
		treePanel.addListener(Events.CheckChange, checkedListener);
		for (final PerceptivePermissionRootModel nprm : allModels)
		{
			final PerceptivePermissionRootModel parent = treeStore.getParent(nprm);
			if (treePanel.isChecked(nprm) && parent != null && !treePanel.isChecked(parent) && parent.getCheckIfAnyChildChecked())
			{
				// Must happen after isDirty is cleared by adminController.
				DeferredCommand.addCommand(new Command() {

					public void execute()
					{
						final Boolean tmpSupress = supressCascade;
						supressCascade = true;
						treePanel.setChecked(parent, true);
						supressCascade = tmpSupress;
					}
				});
			}
		}
	}

	private void expandFullDepth(final ModelData item)
	{
		treePanel.setExpanded((PerceptivePermissionRootModel) item, true);
		for (final PerceptivePermissionRootModel nprm : treeStore.getChildren((PerceptivePermissionRootModel) item))
		{
			expandFullDepth(nprm);
		}
	}
}